package com.pkk.components.encryption.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.apache.commons.lang3.StringUtils;

/**
 * @author peikunkun
 * @version V1.0
 * @url: https://my.oschina.net/u/2519530/blog/536372
 * @Title: myknow
 * @Package com.pkk.encrytion.util
 * @Description: <md5操作>
 * 补充几点：网上有很多Java中MD5的加密实现，和本文所提内容主要区别就是2进制转换16进制
 * <p>
 * 本文是自己写的2进制转换16进制，网上的大部分是利用了java的方法直接转换的，比如
 * <p>
 * Integer.toHexString(bt)
 * <p>
 * 所以碰到这些也不要迷惑，只是实现不同而已。
 * @date 18-5-12 下午12:51
 */
public class MD5Util {


    /**
     * 英语字符可以大写也可以小写，取决于你要的结果是大写还是小写
     */
    private static final char[] HEXDIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    /**
     * MD5
     */
    private final static String DIGEST_MD5 = "MD5";

    /**
     * 对字符串md5加密(小写+字母)
     *
     * @param str 传入要加密的字符串
     * @return MD5加密后的字符串
     */
    public static String getMD5(String str) {
        if (StringUtils.isEmpty(str)) {
            return "";
        }
        try {
            // 生成一个MD5加密计算摘要
            MessageDigest messageDigest = MessageDigest.getInstance(DIGEST_MD5);
            //设置摘要以供进一步使用
            messageDigest.reset();
            // 计算md5函数
            messageDigest.update(str.getBytes());
            //输入的字符进行信息摘要运算，得到一个byte数组这个byte数组是2进制的数据，下一步我们要把2进制转换成16进制
            byte[] bArray = messageDigest.digest();

            String finalStr = new String(encodeHex(bArray, HEXDIGITS));

            System.out.println("16位大写：" + finalStr.substring(8, 24));
            System.out.println("32位大写：" + finalStr);
            return finalStr;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @author kunzai
     * @version V1.0
     * @Title:这个byte数组是2进制的数据，下一步我们要把2进制转换成16进制
     * @Package com.pkk.encrytion.util
     * @Description: <把二进制的数据转换为16进制>
     * @date 18-5-13上午11:32
     * << 和 >>> 都是java的位移操作符
     * <p>
     * 在说明之前需要解释一下2进制到16进制的转换，
     * <p>
     * 把data[i]的高四位(data[i]>>>4) 和0xF0做 & 运算((0xF0 & data[i]) >>> 4)，得到16进制高1位
     * <p>
     * 把data[i]的低四位(data[i]) 和0xF0做 & 运算，得到16进制低1位
     * <p>
     * 上面两句得到的值都是0到15，这样就得到toDigits数组的位置对应的字符。
     * <p>
     * final char[] out = new char[len << 1];
     * 这个语句表示要定义一个输出数组(out[])，长度是需要转换数组(date[])的2倍。
     * <p>
     * 为什么要是2倍呢，因为2进制的128位，要对应16进制的32位，一个byte是八位二进制，也就是2位十六进制字符（2的8次方等于16的2次方）
     */
    public static final char[] encodeHex(final byte[] data, final char[] toDigits) {
        final int len = data.length;
        //扩大2倍，因为以字符等于两字节
        final char[] out = new char[len << 1];
        for (int i = 0, j = 0; i < len; i++) {
            out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
            out[j++] = toDigits[0x0F & data[i]];
        }
        return out;
    }

    public static void main(String[] args) {
        System.out.println(getMD5("admin"));
    }

}

